/**
 * 
 */
package cn.edu.cqu.bysj.wm.ago.lp;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import org.neo4j.tooling.GlobalGraphOperations;
import org.ujmp.core.Matrix;

import scala.util.Random;
import cn.edu.cqu.bysj.wm.ago.anlys.Similarity;
import cn.edu.cqu.bysj.wm.def.ConstDef;
import cn.edu.cqu.bysj.wm.def.PropertyDef;
import cn.edu.cqu.bysj.wm.def.RelTypeDef;
import cn.edu.cqu.bysj.wm.def.Weight;
import cn.edu.cqu.bysj.wm.util.CalcTime;
import cn.edu.cqu.bysj.wm.util.ImportData;


/**
 * @author Meng
 * 
 */
public class LP {

	// adjoin relationships of each node
	public Map<Node, List<Relationship>> adjRel = new HashMap<Node, List<Relationship>>();

	public Matrix matrix;
	public List<Node> nodes = new ArrayList<Node>();
	public GraphDatabaseService gdb;
	public String path;
	public Weight weight;
	public Direction direction;
	public long initTime, iterTime;
	public int n, e = 0;

	/**
	 * @param path
	 * @param gdb
	 * @param weight
	 * @param direction
	 */
	public LP(String path, GraphDatabaseService gdb, Weight weight,
			Direction direction) {
		long t = System.currentTimeMillis();

		this.gdb = gdb;
		this.path = path;
		this.weight = weight;
		this.direction = direction;

		addData();
		initAdj();

		initTime = CalcTime.calcRunTime(t);
	}

	/**
	 * LP interation process
	 */
	public void interationLP() {

		long t = System.currentTimeMillis();

		int time = 1;
		// time less than iterate time
		while (time <= ConstDef.ITERATE_TIME) {

			// random the order of nodes list to update
			Collections.shuffle(nodes);
			for (int i = 0; i < n; i++) {
				Node node = nodes.get(i);

				// point i have adjoin nodes
				if (adjRel.containsKey(node)) {

					// update label as the label in adjoin nodes which has the
					// max frequency
					updateFrequencyLabel(node);
				}
			}
			time++;
		}

		iterTime = CalcTime.calcRunTime(t);
	}

	/**
	 * update label of node
	 * 
	 * @param node
	 */
	private void updateFrequencyLabel(Node node) {

		// the adjoin relationships of node
		List<Relationship> rList = adjRel.get(node);

		// statistics frequency of label
		if (rList.size() != 0) {
			HashMap<Integer, Integer> frequency = new HashMap<Integer, Integer>();

			for (int k = 0; k < rList.size(); k++) {
				Node n = null;
				Relationship r = rList.get(k);

				// propagation direction is according to incoming or outgoing
				// direction
				if (direction.equals(Direction.BOTH)) {
					Node s = r.getStartNode();
					Node e = r.getEndNode();
					if (!s.equals(node)) {
						n = s;
					} else {
						n = e;
					}
					// direction is incoming
				} else if (direction.equals(Direction.INCOMING)) {
					n = r.getStartNode();

					// direction is outgoing
				} else if (direction.equals(Direction.OUTGOING)) {
					n = r.getEndNode();
				}

				int lab = (Integer) n.getProperty(PropertyDef.LABEL);
				int w = (Integer) r.getProperty(PropertyDef.WEIGHT);

				// after considering weight of edge
				if (frequency.containsKey(lab)) {
					frequency.put(lab, frequency.get(lab) + w);
				} else {
					frequency.put(lab, w);
				}
			}

			// max frequency value
			int freq = Collections.max(frequency.values());

			// candidate max frequency label list
			List<Integer> candidate = new ArrayList<Integer>();

			for (int lab : frequency.keySet()) {
				if (frequency.get(lab) == freq) {
					candidate.add(lab);
				}
			}

			// random choose from the candidates
			int label = candidate.get(new Random().nextInt(candidate.size()));

			// update label
			Transaction tx = gdb.beginTx();
			try {
				node.setProperty(PropertyDef.LABEL, label);
				tx.success();
			} finally {
				tx.finish();
			}
		}
	}

	/**
	 * initialize adjoin relationships of each node
	 */
	private void initAdj() {

		for (Node node : GlobalGraphOperations.at(gdb).getAllNodes()) {
			adjRel.put(node, new ArrayList<Relationship>());
			for (Relationship rel : node.getRelationships(direction)) {
				adjRel.get(node).add(rel);
			}
		}
	}

	/**
	 * add data to neo4j graph database
	 */
	public void addData() {

		// import data from file path
		matrix = ImportData.importData(path, weight);
		n = ImportData.n;
		Similarity.n = n;
		Transaction tx = gdb.beginTx();
		try {
			// inital nodes
			for (int i = 0; i < n; i++) {
				Node node = gdb.createNode();
				node.setProperty(PropertyDef.LABEL, i + 1);
				node.setProperty(PropertyDef.SEQUENCE, i + 1);
				nodes.add(node);
			}

			// initial relationships of nodes
			for (int i = 0; i < n; i++) {

				int j;
				// undirected graph only need to create one relationship between
				// two nodes
				if (direction.equals(Direction.BOTH)) {
					j = i + 1;
				} else {
					j = 0;
				}
				for (; j < n; j++) {
					int weight = matrix.getAsInt(i, j);
					if (weight >= 1) {
						e++;
						Relationship rel = nodes.get(i).createRelationshipTo(
								nodes.get(j), RelTypeDef.EDGE);
						rel.setProperty(PropertyDef.WEIGHT, weight);
					}
				}
			}
			tx.success();
		} finally {
			tx.finish();
		}
	}
}
